// array_index datapump implementation
// Copyright 2006 (C) Ralph Thomas

#include <datapumps/array_index.h>
#include <binder.hpp>
#include <adobe/array.hpp>

namespace datapumps {
	class array_index_datapump : public adobe::binder_t {
		adobe::binder_helper_t	array_pump_m;
		adobe::binder_helper_t	current_pump_m;
		adobe::binder_helper_t	index_number_pump_m;
		adobe::binder_helper_t	previous_pump_m;
		adobe::binder_helper_t	next_pump_m;
		adobe::sheet_t&		sheet_m;
		adobe::dictionary_t	params_m;
		//
		// What we actually work on.
		//
		typedef std::map<std::string, unsigned int> index_t;
		typedef std::map<unsigned int, std::string> reverse_index_t;
		index_t			index_m;
		reverse_index_t		reverse_index_m;
		std::string		current_m;
		adobe::name_t		id_m;
		//
		/// Set all the pumps to zero, used when the current
		/// value can't be found in the index.
		//
		void zero_all() {
			index_number_pump_m.set( adobe::value_t( 0 ) );
			previous_pump_m.set( adobe::value_t( std::string() ) );
			next_pump_m.set( adobe::value_t( std::string() ) );
		}
		//
		/// Determine all of the values based on current_m.
		//
		void recompute() {
			index_t::iterator i( index_m.find( current_m ) );
			if( i == index_m.end() ) {
				//
				// Can't find it.
				//
				zero_all();
				return;
			}
			//
			// We add one so that the index starts from one, and so we
			// can use zero as an error state. This may change in the
			// future.
			//
			index_number_pump_m.set( adobe::value_t( i->second + 1) );
			reverse_index_t::iterator prev_it( reverse_index_m.find( i->second - 1 ) );
			if( prev_it != reverse_index_m.end() ) previous_pump_m.set( adobe::value_t( prev_it->second ) );
			else previous_pump_m.set( adobe::value_t( std::string() ) );
			reverse_index_t::iterator next_it( reverse_index_m.find( i->second + 1 ) );
			if( next_it != reverse_index_m.end() ) next_pump_m.set( adobe::value_t( next_it->second ) );
			else next_pump_m.set( adobe::value_t( std::string() ) );
		}
		//
		/// Indexes a single dictionary, and ignores values that
		/// aren't dictionaries.
		///
		/// \param	v	the value containing the dictionary
		///			to index.
		/// \param	i	the index of this dictionary.
		//
		void index_dictionary( const adobe::value_t& v, unsigned int& i ) {
			adobe::dictionary_t	dict;
			std::string		idx;
			//
			// We always need the index to be accurate, even if we have
			// gaps.
			//
			i++;
			if( !v.get( dict ) ) return;
			adobe::dictionary_t::const_iterator val( dict.find( id_m ) );
			if( val == dict.end() ) return;
			if( !val->second.get( idx ) ) return;
			index_m[idx] = i;
			reverse_index_m[i] = idx;
		}
		//
		/// Called when the array changes. It means that we must
		/// reindex.
		///
		/// \param	v	the value containing the array of
		///			dictionaries containing strings.
		//
		void reindex_array( const adobe::value_t& v ) {
			index_m.clear();
			reverse_index_m.clear();
			adobe::array_t array;
			if( !v.get( array ) ) {
				//
				// We need to have an array of dictionaries!
				//
				zero_all();
				return;
			}
			unsigned int index( 0 );
			adobe::for_each( array, boost::bind( &array_index_datapump::index_dictionary, this, _1, boost::ref( index ) ) );
			//
			// Now we need to push new values out.
			//
			recompute();
		}
		//
		/// Update the current item.
		///
		/// \param	v	the current item, in a value.
		//
		void update_current( const adobe::value_t& v ) {
			v.get( current_m );
			recompute();
		}
	  public:
		array_index_datapump( const adobe::dictionary_t& params, adobe::sheet_t& sheet ) : array_pump_m( sheet ), current_pump_m( sheet ), index_number_pump_m( sheet ), previous_pump_m( sheet ), next_pump_m( sheet ), sheet_m( sheet ), params_m( params ) {
			array_pump_m.initialize( params.at( adobe::static_name_t( "array" ) ), boost::bind( &array_index_datapump::reindex_array, this, _1 ), 0 );
			current_pump_m.initialize( params.at( adobe::static_name_t( "current" ) ), boost::bind( &array_index_datapump::update_current, this, _1 ), 0 );
			params.get( adobe::static_name_t( "index_name" ), id_m );
			adobe::dictionary_t::const_iterator i = params.find( adobe::static_name_t( "previous" ) );
			if( i != params.end() ) previous_pump_m.initialize( i->second, 0, 0 );
			i = params.find( adobe::static_name_t( "next" ) );
			if( i != params.end() ) next_pump_m.initialize( i->second, 0, 0 );
			i = params.find( adobe::static_name_t( "index" ) );
			if( i != params.end() ) index_number_pump_m.initialize( i->second, 0, 0 );
		}
		virtual ~array_index_datapump() {}
		virtual void set( const adobe::value_t& val ) {}
		virtual void set_signals( const adobe::sheet_t::monitor_value_t&, const adobe::sheet_t::monitor_active_t& active ) {
			if( active ) active( false );
		}
		virtual void substitute( const adobe::name_t& name, const adobe::value_t& value ) {
			//
			// Most likely this will *never* be called, as it seems incredibly unlikey
			// that this datapump will be used in a generator.
			//
			array_pump_m.substitute( name, value );
			current_pump_m.substitute( name, value );
			index_number_pump_m.substitute( name, value );
			next_pump_m.substitute( name, value );
			previous_pump_m.substitute( name, value );
		}
		virtual void invoke() {}
		virtual adobe::binder_ptr_t copy() const {
			return adobe::binder_ptr_t( new array_index_datapump( params_m, sheet_m ) );
		}
	};
	adobe::value_t make_array_index_datapump( const adobe::dictionary_t& parameters, adobe::sheet_t& sheet ) {
		return adobe::value_t( adobe::binder_ptr_t( new array_index_datapump( parameters, sheet ) ) );
	}
};

